// 2015年版权所有etcd作者
// 
// 根据Apache许可证2.0版（以下简称“许可证”）获得许可；
// 除非遵守许可证，否则不得使用此文件。
// 您可以通过
// 
// www.apache.org/licenses/LICENSE-2.0 
// 
// 除非适用法律要求或书面同意，否则根据许可证分发的软件
// 按“原样”分发，
// 无任何明示或暗示的保证或条件。
// 请参阅许可证，了解管理许可的特定语言和
// 许可证下的限制。

package fileutil

import (
	"fmt"
	"io"
	"os"
	"path/filepath"

	"go.uber.org/zap"
)

const (
	// PrivateFileMode授予所有者读/写文件的权限。
	PrivateFileMode = 0600
)

// IsDirWriteable通过向dir写入和删除文件来检查dir是否可写。如果dir是可写的，则返回nil。
func IsDirWriteable(dir string) error {
	f, err := filepath.Abs(filepath.Join(dir, ".touch"))
	if err != nil {
		return err
	}
	if err := os.WriteFile(f, []byte(""), PrivateFileMode); err != nil {
		return err
	}
	return os.Remove(f)
}

// TouchDirAll与操作系统类似。MkdirAll。如果任何目录
// 不存在，则创建具有0700权限的目录。TouchDirAll还确保给定的目录是可写的。
func TouchDirAll(lg *zap.Logger, dir string) error {
	// 如果path已经是目录，则MkdirAll不执行任何操作并返回nil，因此，
	// 首先检查目录是否以预期的权限模式存在。
	if Exist(dir) {
		err := CheckDirPermission(dir, PrivateDirMode)
		if err != nil {
			lg.Warn("check file permission", zap.Error(err))
		}
	} else {
		err := os.MkdirAll(dir, PrivateDirMode)
		if err != nil {
			// 如果mkdirAll（“a/text”）和“text”不是
			// a目录，则返回syscall。ENOTDIR 
			return err
		}
	}

	return IsDirWriteable(dir)
}

// CreateDirAll与TouchDirAll类似，但如果最深目录不是空的，则返回错误
// 。
func CreateDirAll(lg *zap.Logger, dir string) error {
	err := TouchDirAll(lg, dir)
	if err == nil {
		var ns []string
		ns, err = ReadDir(dir)
		if err != nil {
			return err
		}
		if len(ns) != 0 {
			err = fmt.Errorf("expected %q to be empty, got %q", dir, ns)
		}
	}
	return err
}

// Exist如果文件或目录存在，则返回true。
func Exist(name string) bool {
	_, err := os.Stat(name)
	return err == nil
}

// DirEmpty如果目录为空并且可以访问，则返回true。
func DirEmpty(name string) bool {
	ns, err := ReadDir(name)
	return len(ns) == 0 && err == nil
}

// ZeroToEnd将从SEEK\u CUR开始到SEEK\u结束的文件归零。可能暂时
// 缩短文件长度。
func ZeroToEnd(f *os.File) error {
	// TODO:支持FALLOC\u FL\u ZERO\u RANGE 
	off, err := f.Seek(0, io.SeekCurrent)
	if err != nil {
		return err
	}
	lenf, lerr := f.Seek(0, io.SeekEnd)
	if lerr != nil {
		return lerr
	}
	if err = f.Truncate(off); err != nil {
		return err
	}
	// 确保块保持分配
	if err = Preallocate(f, lenf, true); err != nil {
		return err
	}
	_, err = f.Seek(off, io.SeekStart)
	return err
}

// CheckDirPermission检查现有目录的权限。
// 如果dir为空或存在与指定权限不同的权限，则返回错误。
func CheckDirPermission(dir string, perm os.FileMode) error {
	if !Exist(dir) {
		return fmt.Errorf("directory %q empty, cannot check permission", dir)
	}
	// 检查目录上的现有权限
	dirInfo, err := os.Stat(dir)
	if err != nil {
		return err
	}
	dirMode := dirInfo.Mode().Perm()
	if dirMode != perm {
		err = fmt.Errorf("directory %q exist, but the permission is %q. The recommended permission is %q to prevent possible unprivileged access to the data", dir, dirInfo.Mode(), os.FileMode(PrivateDirMode))
		return err
	}
	return nil
}

// 如果现有目录上的matchFunc为true，则RemoveMatchFile删除文件
// 如果该目录不存在或删除文件失败，则返回错误
func RemoveMatchFile(lg *zap.Logger, dir string, matchFunc func(fileName string) bool) error {
	if lg == nil {
		lg = zap.NewNop()
	}
	if !Exist(dir) {
		return fmt.Errorf("directory %s does not exist", dir)
	}
	fileNames, err := ReadDir(dir)
	if err != nil {
		return err
	}
	var removeFailedFiles []string
	for _, fileName := range fileNames {
		if matchFunc(fileName) {
			file := filepath.Join(dir, fileName)
			if err = os.Remove(file); err != nil {
				removeFailedFiles = append(removeFailedFiles, fileName)
				lg.Error("remove file failed",
					zap.String("file", file),
					zap.Error(err))
				continue
			}
		}
	}
	if len(removeFailedFiles) != 0 {
		return fmt.Errorf("remove file(s) %v error", removeFailedFiles)
	}
	return nil
}
